package singsong;

import java.util.ArrayList;
import java.util.TreeMap;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.UnsupportedAudioFileException;

public class Instrument {
	private ArrayList<Note> notes;
	
	public Instrument(String filename) {
		notes = new ArrayList<Note>();
		Scanner lineByLine = null;
		File instrumentFile = null;
		try {
			instrumentFile = new File(filename);
			lineByLine = new Scanner(instrumentFile);
		} catch (FileNotFoundException e) {
			System.err.println("Instrument filename (" + filename + ") not found.");
			return;
		}
		while (lineByLine.hasNextLine()) {
			Scanner line = new Scanner(lineByLine.nextLine());
			Note note = new Note();
			note.freq = line.nextFloat();
			note.audioClip = RecordedAudioClip.CreateFromFile(
					new File(instrumentFile.getParent(), line.next()).getAbsolutePath());
			notes.add(note);
		}
	}
	
	public RecordedAudioClip record(TrackData data) {
		if (notes.size() == 0) {
			System.err.println("You need some notes in the instrument");
			System.exit(0);
		}
		RecordedAudioClip recordedAudioClip = new RecordedAudioClip(notes.get(0).audioClip.getSamplesPerSecond());
		float t = 0;
		// There are real notes we are capable of playing, and the notes the score says we should play.
		TrackData.Sample lastScoreNote = null;
		for (TrackData.Sample scoreNote : data.getSamples()) {
			Note realNote = getClosestNote(scoreNote.getFrequency());
			float freqRatio = realNote.freq / scoreNote.getFrequency();
			float neededLength = data.getSampleDuration() / freqRatio;
			if (lastScoreNote != null && scoreNote.getFrequency() == lastScoreNote.getFrequency())
				t += neededLength;
			else
				t = 0;
			lastScoreNote = scoreNote;
			ArrayList<Float> portion = realNote.getPortion(t, neededLength, false);
			ArrayList<Float> sampledPortion = sampleAt(portion, freqRatio, scoreNote.getVolume());
			for (float sample : sampledPortion)
			  recordedAudioClip.record(sample);
		}
		return recordedAudioClip;
	}
	
	private ArrayList<Float> sampleAt(ArrayList<Float> samples, float compression, float volume) {
		ArrayList<Float> comp = new ArrayList<Float>();
		for (int f = 0; f < samples.size() * compression; f++) {
			int index = Math.min(samples.size() - 1, (int)(f / compression));
			comp.add(samples.get(index) * volume);
		}
		return comp;
	}
	
	private Note getClosestNote(float freq) {
		float dist = Float.MAX_VALUE;
		Note closest = null;
		for (Note possibleNote : notes) {
			if (Math.abs(possibleNote.freq - freq) < dist) {
				dist = Math.abs(possibleNote.freq - freq);
				closest = possibleNote;
			}
		}
		return closest;
	}
	
	private class Note {
	    public float freq;
	    public RecordedAudioClip audioClip;
	    
	    public ArrayList<Float> getPortion(float start, float length, boolean loop) {
			ArrayList<Float> portion = new ArrayList<Float>();
			int samplesNeeded = (int)(audioClip.getSamplesPerSecond() * length);
			int s = (int)(start * audioClip.getSamplesPerSecond());
			for (int i = 0; i < samplesNeeded; i++) {
				s++;
				if (s > audioClip.getSamples().size() - 1) {
					if (loop) {
						s = 0;
					} else {
						portion.add(0f);
						continue;
					}
						
				}
				portion.add(audioClip.getSamples().get(s));
			}
			return portion;
		}	    
	}
}
